package org.moonlight.jvm.rtda.share.heap.symref;

import org.moonlight.jvm.classfile.constant.info.member.ConstantMethodRefInfo;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;
import org.moonlight.jvm.utils.MethodLookupUtil;

/**
 * 方法符号引用
 *
 * @author Moonlight
 * @createTime 2023/8/30 8:46
 **/
public class MethodRef extends MemberRef {

    private Method method;

    public MethodRef(RtConstantPool rtCp, ConstantMethodRefInfo info) {
        super(rtCp, info);
    }

    public Method resolvedMethod() {
        if (this.method == null) {
            this.resolveMethodRef();
        }
        return this.method;
    }

    /**
     * 解析方法符号引用
     *
     * 如果类D想通过方法符号引用访问类C的某个方法，先要解析符号引用得到类C。
     * 如果C是接口，则抛出IncompatibleClassChangeError异常，否则根据方法名和描述符查找方法。
     * 如果找不到对应的方法，则抛出NoSuchMethodError异常，否则检查类D是否有权限访问该方法。
     * 如果没有，则抛出IllegalAccessError异常。
     *
     * @createTime 9:30 2023/9/12
     * @author moonlight
     **/
    private void resolveMethodRef() {
        // 如果类D想通过方法符号引用访问类C的某个方法，先要解析符号引用得到类C。
        // 如果C是接口，则抛出IncompatibleClassChangeError异常，否则根据方法名和描述符查找方法。
        // 如果找不到对应的方法，则抛出NoSuchMethodError异常，否则检查类D是否有权限访问该方法。
        // 如果没有，则抛出IllegalAccessError异常。
        Class d = this.rtCp.getClazz();
        Class c = this.resolvedClass();
        if (c.isInterface()) {
            throw new IncompatibleClassChangeError();
        }
        Method m = lookupMethod(c, this.name, this.descriptor);
        if (m == null) {
            throw new NoSuchMethodError();
        }
        if (!m.isAccessibleTo(d)) {
            throw new IllegalAccessError();
        }
        this.method = m;
    }
    
    /**
     * 通过 方法名 和 方法描述符 去 类对象 中查找方法
     *   1. 先从 类对象 的继承树中查找
     *   2. 继承树中找不到就去所实现的接口中查找
     *
     * @param clazz 类对象{@link Class}
     * @param methodName 方法名
     * @param methodDesc 方法描述符 {@link org.moonlight.jvm.classfile.constant.info.ConstantNameAndTypeInfo}
     * @return Method
     * @createTime 9:37 2023/9/12
     * @author moonlight
     **/
    public Method lookupMethod(Class clazz, String methodName, String methodDesc) {
        Method m = MethodLookupUtil.lookupMethodInClass(clazz, methodName, methodDesc);
        if (m == null) {
            m = MethodLookupUtil.lookupMethodInInterfaces(clazz.getInterfaces(), methodName, methodDesc);
        }
        return m;
    }

}